package src;

import java.io.*;
import java.util.HashMap;
import java.util.Date;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import javax.swing.JOptionPane;

/**
 * <p>Title: Response.java</p>
 *
 * <p>Description: Used to process incoming requests to the server and generate
 * the appropriate response to that request. This is where most of the server
 * processing is done, thus making it a very important class to the
 * implementation of your server.</p>
 *
 * <p>Copyright: Copyright (c) 2005</p>
 *
 * <p>Company: </p>
 * @author Tracie Hong and Luping May
 * @version 1.0
 */
public class Response {
    Date temp;
    int status;
    HashMap responseCode = new HashMap<Integer, String>();
    Date timeStamp;
    String serverName;
    String conType;
    String version;
    File fileToSend;
    Request req;
    Date lastMod;
    Date expires;
    int age;
   
    
  /**
   * Default constructor for the response object. Variables are reset and/or
   * intialized here. These variables will be used throughout request processing
   * and response generation.
   */
  public Response() {
      //init variables and build response map
      buildResponseMap();
      status = 404;
      serverName = null;
      conType = null;
      timeStamp = null;
      version = null;
      serverName = null;
      fileToSend = null;
      req = new Request();
      lastMod=null;
      expires = null;
      age=0;
  }
  
  public void buildResponseMap(){
      responseCode.put((Integer)200, "OK");
      responseCode.put((Integer)204, "No Content");
      responseCode.put((Integer)302, "Found");
      responseCode.put((Integer)304, "Not Modified");
      responseCode.put((Integer)400, "Bad Request");
      responseCode.put((Integer)401, "Unauthorized");
      responseCode.put((Integer)403, "Forbidden");
      responseCode.put((Integer)404, "Not Found");
      responseCode.put((Integer)500, "Internal Server Error");
      responseCode.put((Integer)501, "Not Implemented");
      
  }//end hashmap builder

  /**
   * Used to process the request that came from the client to the server. There
   * are many things that need to be checked and verified during the processing
   * of a request. You will need to check for authentication, errors, cgi
   * scripts, type of request, etc, and handle each condition appropriately.
   *
   * HINT: it helps to use boolean flags throughout your code to check for the
   * various conditions that may or may not occur.
   *
   * @param myRequest Request object that was generated and stores all the
   *   request information that came in from the client
   * @param MIMETable Hashtable which contains the various mime types the
   *   server can handle.
   * @param env Environment object that contains the environment variables
   *   necessary for cgi script execution.
   */
  public void processRequest(Request myRequest, HashMap MIMETable, Environment env) {
      //Testing using Yoon's checkAuthentication
      timeStamp = new Date();
      
      //end Authentication
      req = myRequest;
      version = myRequest.getVersion();
      serverName = env.getEnv("SERVER_NAME");

      //System.out.println(version);
      fileToSend = new File(env.getEnv("PATH_TRANSLATED"));
      lastMod = new Date( fileToSend.lastModified() );
      /*if( env.isRestricted() )
      {
          //if( env.)
          setStatus(401);
          //JOptionPane jp = new JOptionPane();
          //jp.showInputDialog("BLAHH");
      }
       *
       */
      if(req == null || version == null || serverName == null)
          setStatus(400);
      else if(!fileToSend.exists())
      {
          System.out.println("File not found: 404'd");
          setStatus(404);
      }
      else
      {
          System.out.println("File: " + env.getEnv("PATH_TRANSLATED") + " found!");
          setStatus(200);
          //System.out.println(fileToSend.getAbsolutePath());
          String [] splitMe = fileToSend.getAbsolutePath().split("\\.");
          String ext = "." + splitMe[1];
          System.out.println(" ");
          if (checkMIME(MIMETable, ext) && checkMIMECompatibility(MIMETable, myRequest))    //verify that its in the table
              System.out.println(" ");
              conType = getMIME(MIMETable, ext);    //find and assign mimetype
           
      }
      
      expires = new Date();
      expires.setMinutes((expires.getMinutes()) + 5);//set expiration date to next 5 minutes.
      temp = new Date();
      age = (int) temp.getTime() - (int) timeStamp.getTime();    //now - time response was generated
      
      System.out.println("\n\ntimeStamp = " + timeStamp + "\n\nexpires = " + expires + "\n\n" + "age = "+ age + "\n\n");
      
      //process appropriate method.
      if (req.getMethod().equals("HEAD")) return; //do not send
      else if (req.getMethod().equals("GET")) processGet(myRequest, MIMETable, env);
      else if (req.getMethod().equals("POST")) processPost(myRequest, MIMETable, env);
      else processPut(myRequest, MIMETable, env);
      
      //Checks to see if the file being requested has restricted access, and sets status if true
      if( env.isRestricted() )
      {
          //if( env.)
          String authorization = env.getHeader("Authorization");
          System.out.println("Authorzation header: " + authorization);
          if( authorization != null )
          {
            String splits[] = authorization.split("\\s");
            System.out.println(splits[2]);
            String result = checkAuthentication(splits[1] + " " + splits[2]);
            if( result == null )
            {
                setStatus(403);
                System.out.println("Problem with authentication");
            }
            else
            {
                setStatus(200);
                System.out.println("This should work");
            }
          }
          else setStatus(401);
      }
  }//end processRequest

  /**
   * Used to output a correctly formatted response to the client. This function
   * will need to process any output from a cgi script as well as generate the
   * appropriate headers and body required by an HTTP response.
   *
   * @param output OutputStream object that will be used to send the response
   *   back to the socket.
   */
  public void writeOutput(OutputStream output) throws IOException {
      //setStatus(401);
      //String str = version + " " + status + " " + responseCode.get(status) + "\n";
      
      PrintWriter printer = new PrintWriter(output, true);
      printer.println(version + " " + status + " " + responseCode.get(status));
      if( status == 401 )
          printer.println("WWW-Authenticate: Basic realm=\"Test\"");
      if( status == 403 )
          fileToSend = new File("C:\\Users\\pia\\Desktop\\MyServer\\htdocs\\page403.html");
      printer.println("Date: " + timeStamp);
      printer.println("Server: " + serverName);
      printer.println("Content-Type: " + conType);
      printer.println("Content-Length: " + fileToSend.length());
      printer.println("Last-Modified: " + new Date( fileToSend.lastModified()) );
      printer.println("Expires: " + expires);
      printer.println("Age: " + age + " milliseconds");
      printer.println(); //used to separate body from headers
      System.out.println("Testing before send");
      if( fileToSend.exists() )
      {
      InputStream inFile = new FileInputStream(fileToSend);
      byte [] buff = new byte[1000];
      while (inFile.available() > 0)
      {
          output.write(buff, 0, inFile.read(buff));
      }
      inFile.close();
      System.out.println("Done sending file");
      
      //write to log file.
      //System.out.println("i wanna write this to a file: "+serverName+ " " + timeStamp+" "+req.getCommand()+" "+status +" "+ fileToSend.length());
      writeToLog(serverName+ " " + timeStamp+" "+req.getCommand()+" "+status +" "+ fileToSend.length());
      //System.out.println("I WROTE TO THE LOG!");
      }
 else
      {
     System.out.println("Nothing to send");
     InputStream inFile = new FileInputStream("C:\\Users\\pia\\Desktop\\MyServer\\htdocs\\404.html");
      //BufferedInputStream inputStream = new BufferedInputStream(inFile);


      byte [] buff = new byte[1000];
      while (inFile.available() > 0)
      {
          output.write(buff, 0, inFile.read(buff));
      }
      inFile.close();
      }
      writeToLog(serverName+ " " + timeStamp+" "+req.getCommand()+" "+status +" "+ fileToSend.length());
  }//end writeOutput

  /**
   * Used to test for authentication. If the .htaccess file shows that
   * authentication is needed for access to the file or directory then set the
   * appropriate headers and set the appropriate status codes unless the user
   * has included their authentication. If this is the case, check to make sure
   * their authentication is valid.
   *
   * @param req Request object which is needed to check for authentication
   */
  public void checkAuthentication(Request req){

  }



  /**
   * Used to set the reason for each HTTP status code as designated by the
   * protocol.
   *
   * @param code int value which corresponds to each status code
   */
  public void setStatus(int code) {
      status = (Integer)code;
  }

  /**
   * Private function used to return the appropriate mime type for the file that
   * is being requested
   *
   * @param mimeMap HashMap of mime types from your mime.types file
   * @param extension String value which designates the extension of the file
   *   being requested. This will be used to determine the mime type
   * @return String value that contains the mime type of the file
   */
  private String getMIME(HashMap mimeMap, String extension) {
      Iterator it = mimeMap.entrySet().iterator();
      Entry e = null;
      ArrayList <String> tempArr=new ArrayList<String>();
      
      while(it.hasNext()){
          e = (Entry)it.next();
          if (e.getValue()!=null){
            tempArr = (ArrayList) e.getValue();
            for(int w=0; w<tempArr.size(); w++){
                if (tempArr.contains(extension)){//found the mime type
                      return e.getKey().toString();
                }
            }
          }
      }
       return "application/index";
  }

  /**
   * Private function used to determine whether the mime type requested is a
   * valid mime type
   *0;
   * @param mimeMap HashMap value of the available mime types as designated
   *   by the mime.types file
   * @param extension String value which consists of the extension type
   *   requested. Used to determine the correct mime type
   * @return true if mime type if valid, false otherwise
   */
  private boolean checkMIME(HashMap mimeMap, String extension) {
     if ( mimeMap.containsValue(extension))
         return true;
     return false;
  }

  /**
   * private function used when processing a request from the client. Here, you
   * will check for mime type validity and handle a put request if it is
   * requested. If the request is PUT, you will need to use the body of the
   * request to modify the existing file.
   *
   * @param MIMETable HashMap that contains the valid mime types as
   *   determined by the mime.types file
   * @param body String value that contains the body of the request.
   */
  private void processWithExistence(HashMap MIMETable, String body) {

  }

  /**
   * Private function specifically used to handle output from a cgi script. You
   * will need to check the header passed back from the cgi script to determine
   * the status code of the response. From there, add your headers, attach the
   * body and add any other server directives that need to be included.
   *
   * @param dataOut BufferedOutputStream object that will write to the client
   */
  private void processCGI(BufferedOutputStream dataOut) {

   }

  /**
   * Used to write the appropriate information to the log file.
   *
   * @param logPath String value which contains the location of your log file
   * @param host String value that contains the address of the client who made
   *   the request
   */
  //public void writeToLog(String logPath, String host) {}
  
  public boolean checkMIMECompatibility(HashMap mimes, Request req){
      for (int i=0; i< req.getHeaders().size(); i++){
          if (req.getHeaders().get(i).equals("Accept:")){ //find what browser accepts
              String [] splits = req.getHeaders().get(i).toString().split("\\+s");
              splits = splits[1].split("\\,");
              
              for(int j=0;j<splits.length; j++){
                  if (!conType.equals(splits[j]))
                      return false;
              }
          }
      }
      return true;
  }
  
  private void writeToLog(String writeThis){
      try {
          //System.out.println("Inside the write to log function.");
          BufferedWriter out = new BufferedWriter(new FileWriter("C:\\Users\\pia\\Desktop\\MyServer\\logs\\access.log", true));
          out.write(writeThis);
          out.newLine();
          out.close();
      }
     catch (IOException e)
     {
          System.out.println("Exception caught ");
     }
  }


  //Testing using Yoon's chechAuthentication method
  public static String checkAuthentication(String authenticationString) {//throws InternalServerErrorException {
        StringTokenizer stringTokenizer = new StringTokenizer(authenticationString, " \t");
        String type = stringTokenizer.nextToken();

        Base64Decoder decoder = new Base64Decoder(stringTokenizer.nextToken());
        String namePassword;
        try {
            namePassword = decoder.processString();
        } catch (Base64FormatException e) {
            System.out.println("Incorrectly formatted authentication string: " + e);
            return null;
        }
        stringTokenizer = new StringTokenizer(namePassword, ":");
        String name = stringTokenizer.nextToken();

        String password = stringTokenizer.nextToken();
        try {
            BufferedReader authUserFileReader = new BufferedReader(new FileReader("/home/sean/School/CSC667/"
                    + "MyWebserver/conf/htpasswd.txt"));
            String line = authUserFileReader.readLine();
            while (line != null) {
                if (line.length() == 0) {
                    line = authUserFileReader.readLine();
                    continue;
                }
                stringTokenizer = new StringTokenizer(line, ":");
                String tmpName = stringTokenizer.nextToken();
                if (name.equals(tmpName)) {
                    System.out.println("Found a name! " + tmpName);
                    String tmp = stringTokenizer.nextToken();
                    //decoder = new Base64Decoder(stringTokenizer.nextToken());
                    //String tmp = decoder.processString();
                    System.out.println(tmp);
                    if (password.equals(tmp)) {
                        System.out.println("Found a match?");
                        return name;
                    } else {
                        return null;
                    }
                }
                line = authUserFileReader.readLine();
            }
            authUserFileReader.close();
        } catch (FileNotFoundException e) {
            System.err.println("Error opening AuthUserFile: " + e);
            //throw new InternalServerErrorException();
        } catch (IOException e) {
            System.err.println("Error reading AuthUserFile file: " + e);
            //throw new InternalServerErrorException();
        }// catch (Base64FormatException e) {
           // System.err.println("Incorrectly formatted password string: " + e);
            //throw new InternalServerErrorException();
        //}
        System.out.println("Not finding a match");
        return null;
    }//end checkAuthentication

  

  private void processGet(Request myRequest, HashMap MIMETable, Environment env){
      fileToSend = new File(env.getEnv("PATH_TRANSLATED"));
      if (!fileToSend.exists())
      {
          System.out.println("File not found: 404'd");
          setStatus(400);
      }
      else if ( env.getHeader("If-Modified-Since") != null ){
            String t[] = env.getHeader("If-Modified-Since").split("\\:");
            temp = new Date( t[1] +":"+ t[2]+":"+t[3] );
            if ( lastMod.after(temp) || lastMod.equals(temp) ){
              setStatus(304); //set status to 304 and do nothing.
            }//end
      } //end cache check
      else
      {   
          System.out.println("File: " + env.getEnv("PATH_TRANSLATED") + " found!");
          setStatus(200);
          String [] splitMe = fileToSend.getAbsolutePath().split("\\.");
          String ext = "." + splitMe[1];
          System.out.println(" ");
          if (checkMIME(MIMETable, ext) && checkMIMECompatibility(MIMETable, myRequest))    //verify that its in the table
              System.out.println(" ");
              conType = getMIME(MIMETable, ext);    //find and assign mimetype
          
      }
  }//end processGet
  private void processPut(Request myRequest, HashMap MIMETable, Environment env){}//end process put
  private void processPost(Request myRequest, HashMap MIMETable, Environment env){}//end process post
  

}//
